#pragma once
#include "onlineUsers.hpp"
#include "dbexec.hpp"
#include "logger.hpp"
#include "room.hpp"
#include "session.hpp"
#include "util.hpp"
#include "matcher.hpp"
#include "logger.hpp"

#include <vector>
#include <string>

#define WWWROOT "./wwwroot/"

class gobang_server
{
private:
    std::string _web_root; // ./wwwroot  /register.html -> ./wwroot/register.html
    /*包含所有模块的句柄*/
    user_exec _ue;
    OlineUser_Manager _om;
    Room_Manager _rm;
    Matcher _mc;
    Session_Manage _sm;
    wwserver_t _wserver; // wesocket_server对象
private:
    // 处理静态资源请求响应
    void handle_static_res(wwserver_t::connection_ptr &con)
    {
        // 1、获取请求的url，请求的是什么页面
        websocketpp::http::parser::request req = con->get_request();
        std::string uri = req.get_uri();
        // 2、组合成正确的请求资源路径
        std::string realpath = _web_root + uri;
        // 3、如果请求资源路径是一个目录路径 -> /image -> /image/login.html
        if (realpath.back() == '/')
        {
            realpath += "login.html";
        }
        // 4、读取资源文件中的数据 如果读取不成功返回一个简单的404页面
        std::string body;
        bool ret = file_util::read(realpath, body);
        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<div align = 'center'>😵‍💫💀☠️☠️👻👽👾🤖💩🙈🙉🙊🐵</div>";
            body += "<div align = 'center'><img src = 'https://ts1.cn.mm.bing.net/th/id/R-C.59913c2667c4fd4877b4916806a52852?rik=zaseS9jkTeF16g&riu=http%3a%2f%2f5b0988e595225.cdn.sohucs.com%2fimages%2f20190801%2fa9debd19ab4241b398635466f631d9ef.jpeg&ehk=d5Iyk73zoBslmGMM%2b%2biChMvBd8poMUFqWirmls0GsNo%3d&risl=&pid=ImgRaw&r=0' /></div>";
            body += "<br/>";
            body += "<div align = 'center'>NOT FOUND(你请求的资源不存在!  -> ❎ <-) </div>";
            body += "<br/>";
            body += "<div align = 'center'><h1> ~~~~  404 NOT FOUND  ~~~~</h1></div>";
            body += "</body>";
            body += "</html>";
            con->set_status(websocketpp::http::status_code::not_found);
            con->set_body(body);
            return;
        }
        // 5、将这个资源返回客户端
        con->set_body(body);
        con->set_status(websocketpp::http::status_code::ok);
        return;
    }

    void http_requst(wwserver_t::connection_ptr &con, bool result, websocketpp::http::status_code::value code, const std::string &reason)
    {
        // 将信息进行序列化并添加进行http响应中
        Json::Value json_info;
        json_info["result"] = result;
        json_info["reason"] = reason;
        std::string body;
        json_util::serialize(json_info, body);
        con->set_status(code);
        con->set_body(body);
        con->append_header("Content-Type", "application/json");
        return;
    }

    // 后台注册请求
    void handle_register(wwserver_t::connection_ptr &con)
    {
        // 1、获取注册请求正文
        websocketpp::http::parser::request req = con->get_request();
        std::string req_body = con->get_request_body();
        // 2、将正文反序列化得到用户名以及密码
        Json::Value login_info;
        bool ret = json_util::deserialize(req_body, login_info);
        if (ret == false)
        {
            DLOG("用户注册反序列化失败!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "请求正文格式错误!");
        }
        // 3、用数据库对用户进行新增操作 判断用户名或密码等信息是否有效
        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            DLOG("用户名/密码不完整!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "请输入完整的用户名/密码!");
        }
        ret = _ue.insert(login_info);
        if (ret == false)
        {
            DLOG("向数据库插入用户信息失败!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "该用户名已经存在了!");
        }

        return http_requst(con, true, websocketpp::http::status_code::ok, "用户注册成功!");
    }

    // 后台登录请求功能处理
    void handle_login(wwserver_t::connection_ptr &con)
    {
        // 1、获取登录请求正文
        websocketpp::http::parser::request req = con->get_request();
        std::string req_body = con->get_request_body();
        // 2、将得到的正文进行反序列化得到用户名以及密码
        Json::Value login_info;
        bool ret = json_util::deserialize(req_body, login_info);
        if (ret == false)
        {
            DLOG("用户登录反序列化失败!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "请求正文格式错误!");
        }
        // 3、验证数据的完整性，并进行数据库的登录验证
        if (login_info["username"].isNull() || login_info["password"].isNull())
        {
            DLOG("用户名/密码不完整!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "请输入完整的用户名/密码!");
        }
        ret = _ue.login(login_info); // 用户信息存储在login_info中
        // 4、验证失败返回400响应码
        if (ret == false)
        {
            DLOG("用户名/密码验证错误!");
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "请输入正确的用户名/密码!");
        }
        // 5、成功为用户创建session，并将session添加至响应报头中
        uint64_t uid = login_info["id"].asUInt64();
        session_ptr sp = _sm.create_session(uid, LOGIN); // uid state
        if (sp.get() == nullptr)
        {
            DLOG("创建session会话失败!");
            return http_requst(con, false, websocketpp::http::status_code::internal_server_error, "创建SESSION会话失败!");
        }
        // 设置session的生命周期
        _sm.set_session_date_time(sp->get_ssid(), SESSION_TIMEOUT);
        // 返回Cookie信息中包含session消息
        std::string cookie_body = "SSID=" + std::to_string(sp->get_ssid());
        con->append_header("Set-Cookie", cookie_body);
        return http_requst(con, true, websocketpp::http::status_code::ok, "用户登录成功!");
    }

    bool get_cookie_value(const std::string &cookie_body, const std::string &key, std::string &value)
    {
        // Cookie: SSID=XXX; name=XXX; 根据 （; ）进行分离
        std::vector<std::string> session_body;
        string_util::splict(cookie_body, "; ", session_body);
        for (auto &e : session_body)
        {
            // SSID=XXX name=XXX
            std::vector<std::string> ssid_arr;
            string_util::splict(e, "=", ssid_arr);
            if (ssid_arr.size() != 2)
            {
                continue;
            }
            if (ssid_arr[0] == key)
            {
                value = ssid_arr[1];
                return true;
            }
        }

        return false;
    }

    // 获取用户信息请求
    void handle_gain_info(wwserver_t::connection_ptr &con)
    {
        // 1、获取到Http请求头部中Cookie字段，其中的ssid
        std::string cookie_body = con->get_request_header("Cookie");
        // 1.1、如果没有Cooike信息，返回错误，让用户重新登录
        if (cookie_body.empty())
        {
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "找不到Cookie信息,请重新进行登录!");
        }
        // 2、从Cookie信息中，提取ssid  Cookie: name=value
        std::string ss_sid;
        bool res = get_cookie_value(cookie_body, "SSID", ss_sid);
        // 2.1、如果cookie信息中没有ssid，则让用户重新登录
        if (res == false)
        {
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "Cookie中无ssid信息,请重新进行登录!");
        }
        // 3、拿着ssid去session模块中找session会话信息
        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ss_sid));
        // 3.1、如果没有会话信息，则说明登录过期，让用户重新登录
        if (ssp.get() == nullptr)
        {
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "SESSION信息已经过期,请重新进行登录!");
        }
        // 4、从这个会话信息中拿取用户id，取数据库中查找用户信息
        Json::Value user_info;
        uint64_t uid = ssp->get_user_id();
        res = _ue.select_by_id(uid, user_info); // 信息保存在Json中
        // 4.1、如果找不到用户信息，则让用户重新登录
        if (res == false)
        {
            return http_requst(con, false, websocketpp::http::status_code::bad_request, "数据库中找不到该用户的信息,请重新登录!");
        }
        // 4.2、将用户信息进行序列化，并将数据进行发送
        std::string message;
        json_util::serialize(user_info, message);
        con->set_body(message);
        con->append_header("Content-Type", "application/json");
        con->set_status(websocketpp::http::status_code::ok);
        // 5、重新刷新一下session过期时间
        _sm.set_session_date_time(ssp->get_ssid(), SESSION_TIMEOUT);
    }

    // 处理http请求的回调函数  类内成员-> this
    void handle_http(websocketpp::connection_hdl ch)
    {
        // 读取hmtl中的内容，并获取http通信连接，将其发送
        wwserver_t::connection_ptr wcp = _wserver.get_con_from_hdl(ch);
        websocketpp::http::parser::request req = wcp->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        // 根据不同的请求类型调用不同的回调函数
        if (method == "POST" && uri == "/reg")
        {
            return handle_register(wcp);
        }
        else if (method == "POST" && uri == "/login")
        {
            return handle_login(wcp);
        }
        else if (method == "GET" && uri == "/info")
        {
            return handle_gain_info(wcp);
        }
        else
        {
            return handle_static_res(wcp);
        }
    }

    void websocket_requst(wwserver_t::connection_ptr con, Json::Value &err_info)
    {
        // websocket连接需要使用send
        std::string body;
        json_util::serialize(err_info, body);
        con->send(body);
    }

    void wopen_game_hall(wwserver_t::connection_ptr con)
    {
        // 1、判断用户是否成功登录（根据是否包含session信息判断）
        Json::Value err_info;
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 2、判断这个用户是否是重复登录（只有一次登录）
        if (_om.is_in_hall_user(ssp->get_user_id()) || _om.is_in_room_user(ssp->get_user_id()))
        {
            err_info["optype"] = "hall_ready";
            err_info["result"] = false;
            err_info["reason"] = "该用户在其他地方重复登录!";
            return websocket_requst(con, err_info);
        }
        // 3、给当前用户和连接添加游戏大厅中
        _om.enter_game_hall(ssp->get_user_id(), con);
        // 4、给用户响应进入游戏大厅成功
        err_info["optype"] = "hall_ready";
        err_info["result"] = true;
        websocket_requst(con, err_info);
        // 5、将session设置为永久存在
        _sm.set_session_date_time(ssp->get_ssid(), SESSION_FOREVER);
    }

    void wopen_game_room(wwserver_t::connection_ptr con)
    {
        Json::Value resp_json;
        // 1、获取该玩家的session信息
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 2、判断用户是否在其他地方重复登录(已经在游戏房间或者大厅中)
        if (_om.is_in_hall_user(ssp->get_user_id()) || _om.is_in_room_user(ssp->get_user_id()))
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "玩家在其他地方重复登录!";
            return websocket_requst(con, resp_json);
        }
        // 3、判断在房间管理模块中是否创建房间信息
        room_ptr rrp = _rm.get_room_by_uid(ssp->get_user_id());
        if (rrp.get() == nullptr)
        {
            resp_json["optype"] = "room_ready";
            resp_json["result"] = false;
            resp_json["reason"] = "无该玩家的房间管理信息!";
            return websocket_requst(con, resp_json);
        }
        // 4、将用户信息添加进入房间中，在线用户管理模块
        _om.enter_game_room(ssp->get_user_id(), con);
        // 5、将session信息设置为永久存在
        _sm.set_session_date_time(ssp->get_ssid(), SESSION_FOREVER);
        // 6、给用户回复成功加入房间消息
        resp_json["result"] = true;
        resp_json["optype"] = "room_ready";
        resp_json["room_id"] = (Json::UInt64)rrp->get_room_id();
        resp_json["uid"] = (Json::UInt64)ssp->get_user_id();
        resp_json["white_id"] = (Json::UInt64)rrp->get_whith_id();
        resp_json["black_id"] = (Json::UInt64)rrp->get_black_id();
        return websocket_requst(con, resp_json);
    }

    // 处理websocket握手成功回调函数
    void handle_open(websocketpp::connection_hdl ch)
    {
        // websocket长连接切换成功之后要处理的事件
        wwserver_t::connection_ptr wcp = _wserver.get_con_from_hdl(ch);
        websocketpp::http::parser::request req = wcp->get_request(); // 从请求中获取uri
        std::string uri = req.get_uri();
        // 1、为游戏大厅建立长连接
        if (uri == "/hall")
        {
            return wopen_game_hall(wcp);
        }
        else if (uri == "/room")
        {
            // 2、为游戏房间建立长连接（页面websocket连接断开重新建立长连接）
            return wopen_game_room(wcp);
        }
    }

    void wclose_game_hall(wwserver_t::connection_ptr con)
    {
        // 判断这个用户是否成功登录过
        Json::Value err_info;
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 1、从游戏大厅中移除该玩家用户
        _om.exit_game_hall(ssp->get_user_id());
        // 2、将session设置为临时生命周期
        _sm.set_session_date_time(ssp->get_ssid(), SESSION_TIMEOUT);
    }

    void wclose_game_room(wwserver_t::connection_ptr con)
    {
        // 1、获取session信息，识别客户端
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 2、将玩家从在线用户管理模块中移除
        _om.exit_game_room(ssp->get_user_id());
        // 3、设置session的生命周期
        _sm.set_session_date_time(ssp->get_ssid(), SESSION_TIMEOUT);
        // 4、将玩家从房间中移除房间管理模块
        _rm.remove_room_by_uid(ssp->get_user_id());
    }

    // 处理websocket连接断开回调函数
    void handle_close(websocketpp::connection_hdl ch)
    {
        // 1、需要获取uri，进行判断是房间的还是大厅的长连接断开
        wwserver_t::connection_ptr conn = _wserver.get_con_from_hdl(ch);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            // 建立了游戏大厅的长连接
            return wclose_game_hall(conn);
        }
        else if (uri == "/room")
        {
            return wclose_game_room(conn);
        }
    }
    session_ptr handle_session_by_cookie(wwserver_t::connection_ptr con)
    {
        //   1、获取到Http请求头部中Cookie字段，其中的ssid
        Json::Value err_info;
        std::string cookie_body = con->get_request_header("Cookie");
        // 1.1、如果没有Cooike信息，返回错误，让用户重新登录
        if (cookie_body.empty())
        {
            err_info["optype"] = "hall_ready";
            err_info["result"] = false;
            err_info["reason"] = "找不到Cookie信息,请重新登录!";
            websocket_requst(con, err_info);
            return session_ptr();
        }
        // 2、从Cookie信息中，提取ssid  Cookie: name=value
        std::string ss_sid;
        bool res = get_cookie_value(cookie_body, "SSID", ss_sid);
        // 2.1、如果cookie信息中没有ssid，则让用户重新登录
        if (res == false)
        {
            err_info["optype"] = "hall_ready";
            err_info["result"] = false;
            err_info["reason"] = "Cookie中无ssid信息,请重新登录!";
            websocket_requst(con, err_info);
            return session_ptr();
        }
        // 3、拿着ssid去session模块中找session会话信息
        session_ptr ssp = _sm.get_session_by_ssid(std::stol(ss_sid));
        // 3.1、如果没有会话信息，则说明登录过期，让用户重新登录
        if (ssp.get() == nullptr)
        {
            err_info["optype"] = "hall_ready";
            err_info["result"] = false;
            err_info["reason"] = "SESSION信息已经过期,请重新登录!";
            websocket_requst(con, err_info);
            return session_ptr();
        }
        // 4、从这个会话信息中拿取用户id，取数据库中查找用户信息
        Json::Value user_info;
        uint64_t uid = ssp->get_user_id();
        res = _ue.select_by_id(uid, user_info); // 信息保存在Json中
        // 4.1、如果找不到用户信息，则让用户重新登录
        if (res == false)
        {
            err_info["optype"] = "hall_ready";
            err_info["result"] = false;
            err_info["reason"] = "数据库中找不到该用户的信息,请重新登录!";
            websocket_requst(con, err_info);
            return session_ptr();
        }
        return ssp;
    }

    void wmessage_game_hall(wwserver_t::connection_ptr con, wwserver_t::message_ptr mptr)
    {
        Json::Value resp_json;
        std::string resp_body;
        // 1、通过连接判断是否是正常登录，以及用户信息
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 2、获取请求信息
        Json::Value req_json;
        std::string req_body = mptr->get_payload();
        bool ret = json_util::deserialize(req_body, req_json);
        if (ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求反序列化解析错误";
            return websocket_requst(con, resp_json);
        }
        // 3、对请求信息进行处理
        if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_start")
        {
            // 3.1 匹配请求，将用户添加到匹配队列中
            _mc.add_math_queue(ssp->get_user_id());
            resp_json["optype"] = "match_start";
            resp_json["result"] = true;
            resp_json["reason"] = "成功添加到匹配队列中!";
            return websocket_requst(con, resp_json);
        }
        else if (!req_json["optype"].isNull() && req_json["optype"].asString() == "match_stop")
        {
            // 3.2 取消匹配请求，将用户从匹配队列中移除
            _mc.del_math_queue(ssp->get_user_id());
            resp_json["optype"] = "match_stop";
            resp_json["result"] = true;
            resp_json["reason"] = "成功从匹配队列中移除!";
            return websocket_requst(con, resp_json);
        }
        // 未知请求类型
        resp_json["optype"] = "UNKOWN";
        resp_json["result"] = false;
        resp_json["reason"] = "请求类型未知!";
        return websocket_requst(con, resp_json);
    }

    void wmessage_game_room(wwserver_t::connection_ptr con, wwserver_t::message_ptr mptr)
    {
        Json::Value resp_json;
        // 1、获取session会话信息，判断客户端是否是正常登录
        session_ptr ssp = handle_session_by_cookie(con);
        if (ssp.get() == nullptr)
        {
            return;
        }
        // 2、获取客户端房间信息
        room_ptr rp = _rm.get_room_by_uid(ssp->get_user_id());
        if (rp.get() == nullptr)
        {
            resp_json["optype"] = "UNKOWN";
            resp_json["result"] = false;
            resp_json["reason"] = "用户房间信息获取失败!";
            return websocket_requst(con, resp_json);
        }
        // 3、对房间消息进行反序列化 获取房间消息
        Json::Value rep_json;
        std::string message = mptr->get_payload();
        bool ret = json_util::deserialize(message, rep_json);
        if (ret == false)
        {
            resp_json["optype"] = "UNKOWN";
            resp_json["result"] = false;
            resp_json["reason"] = "房间信息反序列化失败!";
            return websocket_requst(con, resp_json);
        }
        // 4、通过房间模块消息处理发送
        rp->handle_total(rep_json);
    }

    // 处理websocket连接收到消息的回调函数
    void handle_message(websocketpp::connection_hdl ch, wwserver_t::message_ptr mptr)
    {
        // 通过uri去判断
        wwserver_t::connection_ptr wcp = _wserver.get_con_from_hdl(ch);
        websocketpp::http::parser::request req = wcp->get_request();
        std::string uri = req.get_uri();
        if (uri == "/hall")
        {
            return wmessage_game_hall(wcp, mptr);
        }
        else if (uri == "/room")
        {
            return wmessage_game_room(wcp, mptr);
        }
    }

public:
    /*服务器的搭建，业务逻辑的处理*/
    gobang_server(const std::string &host,
                  const std::string &user,
                  const std::string &passwd,
                  const std::string &dbname,
                  uint16_t port = 3306,
                  const std::string &web_root = WWWROOT)
        : _web_root(web_root),
          _ue(host, user, passwd, dbname, port),
          _rm(&_ue, &_om),
          _sm(&_wserver),
          _mc(&_rm, &_om, &_ue)
    {
        // 1、设置日志等级，禁止打印所有日期等级
        _wserver.set_access_channels(websocketpp::log::alevel::none);
        // 2、初始化asio框架调度器，并设置地址复用
        _wserver.init_asio();
        _wserver.set_reuse_addr(true);
        // 3、设置业务处理逻辑的回调函数
        _wserver.set_http_handler(std::bind(&gobang_server::handle_http, this, std::placeholders::_1));
        _wserver.set_open_handler(std::bind(&gobang_server::handle_open, this, std::placeholders::_1));
        _wserver.set_close_handler(std::bind(&gobang_server::handle_close, this, std::placeholders::_1));
        _wserver.set_message_handler(std::bind(&gobang_server::handle_message, this, std::placeholders::_1, std::placeholders::_2));
    }

    /*根据端口号启动服务器*/
    void start(int port)
    {
        _wserver.listen(port);
        // 获取新连接
        _wserver.start_accept();
        _wserver.run();
    }
};